﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Xml;
using System.IO;
using System.Text.RegularExpressions;
using log4net;
using CodeMadeSimple.Common.Validation;
using CodeMadeSimple.PropertiesManagement;

namespace EnvRide.Core.ObjectModel
{
    /// <summary>
    /// Represents the inclusion of another override.
    /// </summary>
    public abstract class OverrideInclude : IXmlSerializable, IValidatable
    {
        #region Fields

        private string _overrideName;
        private string _environmentName;
        private string _filePath;
        private string _if;
        private EnvRideProject _project;
        private Environment _environment;
        private string _propertiesPrefix;
        private string _propertiesContainer;

        #endregion

        #region Properties

        /// <summary>
        /// Project that this instance belongs to.
        /// </summary>
        public EnvRideProject Project
        {
            get { return _project; }
            set { _project = value; }
        }

        /// <summary>
        /// Environment that this instance belongs to.
        /// </summary>
        public Environment Environment
        {
            get { return _environment; }
            set { _environment = value; }
        }

        /// <summary>
        /// Name of the override to include. If its not specified it will include all overrides
        /// of the matching environment.
        /// </summary>
        public string OverrideName
        {
            get { return _overrideName; }
            set { _overrideName = value; }
        }

        /// <summary>
        /// Name of the environment of the override to include. If its not specified, it will look
        /// in the environment with the same name as the current.
        /// </summary>
        public string EnvironmentName
        {
            get { return _environmentName; }
            set { _environmentName = value; }
        }

        /// <summary>
        /// Path to the file that contains the override to include.
        /// </summary>
        public string FilePath
        {
            get { return _filePath; }
            set { _filePath = value; }
        }

        /// <summary>
        /// A csharp expression that should evaluate to a boolean value indicating if the include should be processed or not.
        /// </summary>
        public string If
        {
            get { return _if; }
            set { _if = value; }
        }

        /// <summary>
        /// The properties container to set as current while processing the override.
        /// </summary>
        public string NewPropertyContainer
        {
            get { return _propertiesContainer; }
            set { _propertiesContainer = value; }
        }

        /// <summary>
        /// The prefix prepended to all properties references on the included override(s).
        /// </summary>
        public string PropertiesPrefix
        {
            get { return _propertiesPrefix; }
            set { _propertiesPrefix = value; }
        }

        #endregion

        /// <summary>
        /// Parse this instance fields values looking properties references.
        /// </summary>
        /// <param name="args">Process arguments.</param>
        internal void ParseProperties(EnvRideProcessArguments args)
        {
            FilePath = args.ParseValue(FilePath);
            OverrideName = args.ParseValue(OverrideName);
            EnvironmentName = args.ParseValue(EnvironmentName);
        }

        #region IXmlSerializable Members

        public System.Xml.Schema.XmlSchema GetSchema()
        {
            throw new NotImplementedException();
        }

        public void ReadXml(XmlReader reader)
        {
            OverrideName = reader.GetAttribute("overrideName");

            EnvironmentName = reader.GetAttribute("environmentName");

            FilePath = reader.GetAttribute("filepath");

            If = reader.GetAttribute("if");

            PropertiesPrefix = reader.GetAttribute("propPrefix");

            NewPropertyContainer = reader.GetAttribute("newPropContainer");
        }

        public void WriteXml(XmlWriter writer)
        {
            if (!String.IsNullOrEmpty(OverrideName))
                writer.WriteAttributeString("overrideName", OverrideName);

            if (!String.IsNullOrEmpty(EnvironmentName))
                writer.WriteAttributeString("environmentName", EnvironmentName);

            if (!String.IsNullOrEmpty(FilePath))
                writer.WriteAttributeString("filepath", FilePath);

            if (!String.IsNullOrEmpty(If))
                writer.WriteAttributeString("if", If);

            if (!String.IsNullOrEmpty(PropertiesPrefix))
                writer.WriteAttributeString("propPrefix", PropertiesPrefix);

            if (!String.IsNullOrEmpty(NewPropertyContainer))
                writer.WriteAttributeString("newPropContainer", NewPropertyContainer);

        }

        /// <summary>
        /// Process the override inclusion.
        /// </summary>
        /// <param name="args">Processing arguments.</param>	
        public void Process(EnvRideProcessArguments args)
        {
            if (args == null)
                throw new ArgumentNullException("args");

            using (ThreadContext.Stacks[EnvRideProject.ENV_RIDE_LOG_STACK].Push(this.ToString()))
            {
                String previousPropertiesPrefix = args.PropertiesPrefix;
                IPropertyContainer previousPropContainer = args.PropertyContainer;

                try
                {
                    if (args.ParseIfExpression(If))
                    {
                        if (EnvRideProject.Logger.IsDebugEnabled)
                            EnvRideProject.Logger.Debug("Processing...");

                        EnvRideProject project = GetReferencedProject(args);

                        Environment env = GetReferencedEnvironment(project, args);

                        if (!String.IsNullOrEmpty(PropertiesPrefix))
                        {
                            if (EnvRideProject.Logger.IsDebugEnabled)
                                EnvRideProject.Logger.DebugFormat("Changing PropertiesPrefix from \"{0}\" to \"{1}\"", args.PropertiesPrefix, PropertiesPrefix);

                            args.PropertiesPrefix = PropertiesPrefix;
                        }

                        if (!String.IsNullOrEmpty(NewPropertyContainer))
                        {
                            if (args.PropertyContainer == null)
                                throw new ArgumentException("Cannot change to a new property container if the current its null!");

                            args.PropertyContainer = args.PropertyContainer.Switch(NewPropertyContainer, args.PropertyNotFoundAction);
                        }

                        if (env != null)
                        {
                            if (String.IsNullOrEmpty(OverrideName))
                            {
                                AddAllOverrides(env, args);
                            }
                            else
                            {
                                // Includes the comma separated list of overrides
                                String[] overrideNames = OverrideName.Split(',');

                                for (int i = 0; i < overrideNames.Length; i++)
                                {
                                    String overName = overrideNames[i].Trim();

                                    if (EnvRideProject.Logger.IsDebugEnabled)
                                        EnvRideProject.Logger.DebugFormat("Including \"{0}\"...", overName);

                                    Environment currentEnv = env;
                                    Override over = null;
                                    int pushedEnvs = 0;

                                    // Looks for the overrides on the environment hierarchy chain starting from the bottom
                                    while (over == null && currentEnv != null)
                                    {
                                        ThreadContext.Stacks[EnvRideProject.ENV_RIDE_LOG_STACK].Push(currentEnv.ToString());
                                        pushedEnvs++;

                                        over = GetOverride(overName, currentEnv);

                                        if (over == null)
                                            currentEnv = currentEnv.GetBaseEnvironment(args);
                                    }

                                    for (int j = 0; j < pushedEnvs; j++)
                                    {
                                        ThreadContext.Stacks[EnvRideProject.ENV_RIDE_LOG_STACK].Pop();
                                    }

                                    if (over != null)
                                    {
                                        over.Process(args);
                                    }
                                    else
                                    {
                                        args.HasErrors = true;
                                        EnvRideProject.Logger.ErrorFormat("The override \"{0}\" was not found!", overName);
                                    }
                                }
                            }
                        }
                    }
                }
                finally
                {
                    if (!String.IsNullOrEmpty(PropertiesPrefix))
                    {
                        if (EnvRideProject.Logger.IsDebugEnabled)
                            EnvRideProject.Logger.DebugFormat("Returning PropertiesPrefix to \"{0}\"", previousPropertiesPrefix);

                        args.PropertiesPrefix = previousPropertiesPrefix;
                    }

                    args.PropertyContainer = previousPropContainer;
                }
            }
        }

        /// <summary>
        /// Get the referenced Environment.
        /// </summary>
        /// <param name="project">Referenced EnvRideProject.</param>
        /// <param name="args">Processing arguments.</param>	
        /// <returns>Referenced environment.</returns>
        public Environment GetReferencedEnvironment(EnvRideProject project, EnvRideProcessArguments args)
        {
            String envName = args.ParseValue(EnvironmentName);

            Environment env = project.Environments[envName];

            if (env == null)
            {
                args.HasErrors = true;
                EnvRideProject.Logger.ErrorFormat("The environment \"{0}\" was not found at project \"{1}\"!", envName, project);
            }

            return env;
        }

        /// <summary>
        /// Returns the referenced EnvRideProject.
        /// </summary>
        /// <param name="args">Processing arguments.</param>	
        /// <returns>Referenced EnvRideProject.</returns>
        public EnvRideProject GetReferencedProject(EnvRideProcessArguments args)
        {
            EnvRideProject project = null;

            // Checks if it the environment to include its on the current project or in another
            if (String.IsNullOrEmpty(FilePath))
            {
                project = Project;
            }
            else
            {
                String filePath = args.ParseValue(FilePath);

                project = EnvRideProject.Deserialize(Project.BaseDirectory, filePath);
            }

            return project;
        }

        private void AddAllOverrides(Environment env, EnvRideProcessArguments args)
        {
            Environment baseEnv = env.GetBaseEnvironment(args);

            if (baseEnv != null)
            {
                using (ThreadContext.Stacks[EnvRideProject.ENV_RIDE_LOG_STACK].Push(baseEnv.ToString()))
                {
                    AddAllOverrides(baseEnv, args);
                }
            }

            AddAllOverridesInternal(env, args);
        }

        protected abstract Override GetOverride(String overrideName, ObjectModel.Environment env);

        protected abstract void AddAllOverridesInternal(ObjectModel.Environment env, EnvRideProcessArguments args);

        #endregion

        #region IValidatable Members

        public string GetValidationId()
        {
            return ToString();
        }

        public ValidationResult Validate()
        {
            ValidationResult result = new ValidationResult(this);

            if (EnvironmentName == null)
            {
                result.AddMessage(new RequiredValueValidationMessage("EnvironmentName"));
            }

            return result;
        }

        #endregion
    }
}
